package indi.zhang.framework.core.aop;


import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;

import indi.zhang.common.base.Result;
import indi.zhang.common.tools.JsonUtils;
import indi.zhang.framework.annotation.OperationLogAnno;
import indi.zhang.framework.core.util.CommonUtils;
import indi.zhang.framework.entity.exceptionLogModule.ExceptionLog;
import indi.zhang.framework.entity.operationLogModule.LogEntity;
import indi.zhang.framework.entity.operationLogModule.OperationLog;
import indi.zhang.framework.enums.OperationType;
import indi.zhang.framework.service.ExceptionLogService;
import indi.zhang.framework.service.OperationLogService;
import indi.zhang.framework.tools.CurrentUserUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

/**
 * 全局日志
 *
 * @author jy
 * @since 2020-07-15 09:19
 */
@Aspect
@Component
@Slf4j
public class LogRecordAspect {

	@Autowired
	private OperationLogService operationLogService;

	@Autowired
	private ExceptionLogService exceptionLogService;

	/**
	 * 设置操作日志切入点 记录操作日志 在注解的位置切入代码
	 */
	@Pointcut("@annotation(operationLogAnno)")
	public void operLogPointCut(OperationLogAnno operationLogAnno) {
	}

	/**
	 * 设置操作异常切入点记录异常日志 扫描所有controller包下操作
	 */
	@Pointcut("execution(* indi.zhang.framework.controller..*.*(..))")
	public void excludeService() {
	}


	@AfterReturning(value = "operLogPointCut(operationLogAnno)", returning = "keys", argNames = "joinPoint,keys,operationLogAnno")
	public void doAround(JoinPoint joinPoint, Object keys, OperationLogAnno operationLogAnno) {

		OperationLog operationLog = new OperationLog();

		if (keys instanceof Result) {
			// 返回结果
			operationLog.setRespContent(StrUtil.maxLength(JsonUtils.objectToJson(keys), 950));
		}
		else {
			// 返回结果
			operationLog.setRespContent("返回格式非JsonData");
		}

		// 请求用户ID
		operationLog.setUserId(CurrentUserUtil.getUserId());
		// 请求用户名称
		operationLog.setUserName(CurrentUserUtil.getName());
		// 请求IP
		operationLog.setIp(CommonUtils.getClientIp());
		setRequestParamsAndMethodName(operationLog, joinPoint);
		processingOperationLog(operationLog, operationLogAnno);

	}

	@Async("taskExecutor")
	void processingOperationLog(OperationLog operationLog, OperationLogAnno operationLogAnno) {
		if (operationLogAnno != null) {
			String module = operationLogAnno.module();
			OperationType type = operationLogAnno.type();
			String desc = operationLogAnno.desc();
			// 操作模块
			operationLog.setModule(module);
			// 操作类型
			operationLog.setType(type.toString());
			// 操作描述
			operationLog.setDescription(desc);
		}
		operationLogService.save(operationLog);
	}

	/**
	 * 异常返回通知，用于拦截异常日志信息 连接点抛出异常后执行
	 *
	 * @param joinPoint 切入点
	 * @param e         异常信息
	 */
	@AfterThrowing(pointcut = "excludeService()", throwing = "e")
	public void saveExceptionLog(JoinPoint joinPoint, Throwable e) {
		ExceptionLog exceptionLog = new ExceptionLog();
		// 操作员ID
		exceptionLog.setUserId(CurrentUserUtil.getUserId());
		// 操作员名称
		exceptionLog.setUserName(CurrentUserUtil.getName());
		// 操作员IP
		exceptionLog.setIp(CommonUtils.getClientIp());
		setRequestParamsAndMethodName(exceptionLog, joinPoint);
		processingExceptionLog(exceptionLog, e);
	}

	@Async("taskExecutor")
	void processingExceptionLog(ExceptionLog exceptionLog, Throwable e) {
		// 异常名称
		exceptionLog.setName(e.getClass().getName());
		// 异常信息
		exceptionLog.setMessage(StrUtil.maxLength(stackTraceToString(e.getClass().getName(), e.getMessage(), e.getStackTrace()), 950));
		exceptionLogService.save(exceptionLog);
	}

	/**
	 * 转换异常信息为字符串
	 *
	 * @param exceptionName    异常名称
	 * @param exceptionMessage 异常信息
	 * @param elements         堆栈信息
	 */
	public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
		StringBuilder sb = new StringBuilder();
		sb.append(exceptionName).append(":").append(exceptionMessage).append("\n\t");
		for (StackTraceElement stet : elements) {
			sb.append(stet).append("\n");
		}
		return sb.toString();
	}

	/**
	 * 封装 日志信息
	 *
	 * @param logEntity 日志对象
	 * @param joinPoint 切入点
	 */
	private void setRequestParamsAndMethodName(LogEntity logEntity, JoinPoint joinPoint) {
		RequestAttributes ra = RequestContextHolder.getRequestAttributes();
		ServletRequestAttributes sra = (ServletRequestAttributes) ra;
		assert sra != null;
		HttpServletRequest request = sra.getRequest();
		String httpMethod = request.getMethod();
		String queryString = request.getQueryString();
		Object[] args = joinPoint.getArgs();
		String params = "";
		if (args.length > 0) {
			if ("GET".equals(httpMethod) || "DELETE".equals(httpMethod)) {
				params = queryString;
			}
			else {
				for (int i = 0; i < args.length; i++) {
					if (args[i] instanceof ServletRequest) {
						args[i] = ((HttpServletRequest) args[i]).getParameterMap();
					}
					else if (args[i] instanceof ServletResponse) {
						args[i] = "response对象";
					}
					else if (args[i] instanceof MultipartFile) {
						args[i] = "MultipartFile对象";
					}
					else if (args[i] instanceof MultipartFile[]) {
						args[i] = "MultipartFile数组对象";
					}
					else if (args[i] instanceof BindingResult) {
						args[i] = "BindingResult对象";
					}
				}
				params = JSONUtil.toJsonStr(args);
			}
		}
		// 从切面织入点处通过反射机制获取织入点处的方法
		MethodSignature signature = (MethodSignature) joinPoint.getSignature();
		// 获取切入点所在的方法
		Method method = signature.getMethod();
		// 获取请求的类名
		String className = joinPoint.getTarget().getClass().getName();
		// 获取请求的方法名
		String methodName = className + "." + method.getName();
		// 请求参数
		logEntity.setRequestParam(StrUtil.maxLength(params, 450));
		// 请求方法名
		logEntity.setMethod(methodName);
		// 操作URI
		logEntity.setUri(request.getRequestURI());

	}

}
